<!--
  * @description:去到底部和回到顶部的通用组件
  * @author:huangpeng
  * @date:2024年07月24日11:20
-->
<template>
  <transition name="el-fade-in">
    <div class="back-top-or-bottom-container" :style="{'right': styleRight, 'bottom': styleBottom}">
      <el-tooltip v-if="visibleToBottom" content="去到底部" placement="left" effect="light">
        <div class="back-top-or-bottom-nav-item scroll-nav-bottom" @click.stop="handleToBottomClick">
          <el-icon class="el-icon-caret-bottom"></el-icon>
        </div>
      </el-tooltip>
      <el-tooltip v-if="visibleToTop" content="回到顶部" placement="left" effect="light">
        <div class="back-top-or-bottom-nav-item scroll-nav-top" @click.stop="handleToTopClick">
          <el-icon class="el-icon-caret-top"></el-icon>
        </div>
      </el-tooltip>
    </div>
  </transition>
</template>

<script>
import throttle from 'throttle-debounce/throttle';

const cubic = value => Math.pow(value, 3);
const easeInOutCubic = value => value < 0.5
    ? cubic(value * 2) / 2
    : 1 - cubic((1 - value) * 2) / 2;

export default {
  name: 'ElBacktop',

  props: {
    /** 距离顶部多少高度开始出现回到顶部，距离底部多少高度显示去到底部 */
    visibilityHeight: {
      type: Number,
      default: 200
    },
    target: [String],
    right: {
      type: Number,
      default: 40
    },
    bottom: {
      type: Number,
      default: 100
    }
  },

  data() {
    return {
      /** 滚动元素 */
      el: null,
      /* document */
      container: null,
      /** 显示回到顶部 */
      visibleToTop: false,
      /** 显示回到底部 */
      visibleToBottom: true,
      /** 是否点击了去到底部或者回到顶部 */
      dvClickToTopOrBottom: false,
    };
  },

  computed: {
    styleBottom() {
      return `${this.bottom}px`;
    },
    styleRight() {
      return `${this.right}px`;
    }
  },

  mounted() {
    this.init();
    this.throttledScrollHandler = throttle(300, this.onScroll);
    this.container.addEventListener('scroll', this.throttledScrollHandler);
  },

  methods: {
    init() {
      this.container = document;
      this.el = document.documentElement;
      if (this.target){
        const scrollEls = document.querySelectorAll(this.target);
        for (let scrollEl of scrollEls){
          if (scrollEl && scrollEl.childNodes.length > 0){
            this.el = scrollEl;
            this.container = this.el;
          }
        }
      }
    },
    onScroll() {
      if (this.dvClickToTopOrBottom){
        return;
      }
      const scrollTop = this.el.scrollTop;
      const calcHeight = Math.trunc(this.el.scrollHeight) - Math.trunc(this.el.offsetHeight) - Math.trunc(this.el.scrollTop);
      this.visibleToTop = scrollTop >= this.visibilityHeight;
      this.visibleToBottom = calcHeight >= this.visibilityHeight;
    },
    /** 点击去到底部 */
    handleToBottomClick(e){
      this.scrollToBottom();
      this.$emit ('click',e);
    },
    /** 点击回到顶部 */
    handleToTopClick(e){
      this.scrollToTop();
      this.$emit ('click',e);
    },
    /** 滚动到底部 */
    scrollToBottom(){
      this.dvClickToTopOrBottom = true;
      const el = this.el;
      const beginTime = Date.now();
      const endValue = el.scrollHeight;
      const startValue = el.scrollTop;
      const rAF = window.requestAnimationFrame || (func => setTimeout(func, 16));
      const frameFunc = () => {
        const progress = (Date.now() - beginTime) / 500;
        if (progress < 1) {
          el.scrollTop = startValue + (endValue -startValue) * easeInOutCubic(progress);
          rAF(frameFunc);
        } else {
          el.scrollTop = el.scrollHeight;
          this.visibleToTop = true;
          this.visibleToBottom = false;
          this.dvClickToTopOrBottom = false;
        }
      };
      rAF(frameFunc);
    },
    /** 滚动到顶部 */
    scrollToTop() {
      this.dvClickToTopOrBottom = true;
      const el = this.el;
      const beginTime = Date.now();
      const beginValue = el.scrollTop;
      const rAF = window.requestAnimationFrame || (func => setTimeout(func, 16));
      const frameFunc = () => {
        const progress = (Date.now() - beginTime) / 500;
        if (progress < 1) {
          el.scrollTop = beginValue * (1 - easeInOutCubic(progress));
          rAF(frameFunc);
        } else {
          el.scrollTop = 0;
          this.visibleToTop = false;
          this.visibleToBottom = true;
          this.dvClickToTopOrBottom = false;
        }
      };
      rAF(frameFunc);
    }
  },

  beforeDestroy() {
    this.container.removeEventListener('scroll', this.throttledScrollHandler);
  }
};
</script>
<style lang="less" scoped>
.back-top-or-bottom-container {
  position:fixed;
  height:94px;
  display:flex;
  flex-direction:column;
  align-items:center;
  justify-content:space-between;
}
.back-top-or-bottom-nav-item {
  width: 40px;
  height: 40px;
  font-size: 20px;
  text-align: center;
  line-height: 40px;
  display: flex;
  align-items: center;
  justify-content: center;
  color: #1989fa;
  cursor: pointer;
  background-color: #ffffff;
  border-radius: 50%;
  box-shadow: 0 0 6px rgba(0, 0, 0, 0.12);
}
.back-top-or-bottom-nav-item:hover {
  background-color:#f2f6fc;
}
#custom-back-top-or-bottom {
  display: none;
}

@media only screen and (max-width:850px) {

  .back-top-or-bottom-nav-item {
    background-color: rgb(255 255 255 / 100%);
  }
  .back-top-or-bottom-nav-item:hover {
    background-color: rgb(246, 246, 246);
  }
  #custom-back-top-or-bottom {
    display: flex !important;
  }
}
@media only screen and (min-width:850px){
  #custom-back-top-or-bottom {
    display: none !important;
  }
}
</style>
